#! /usr/bin/env python
# MMapArea.py
# This file is part of CharTr
#
# Copyright (C) 2006 - Don Scorgie <Don@Scorgie.org> (labyrinth part)
# Copyright (C) 2008 - Nicolas Bigeard <nico.bigeard@gmail.com>  
# CharTr is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# CharTr is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CharTr; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA  02110-1301  USA
#

import math

import time
import gtk
import pango
import gobject
import gettext
import copy
import cairo
_ = gettext.gettext

import xml.dom.minidom as dom

import Links
import TextThought
import NoteThought
import ImageThought
import AudioThought
#import MathThought
import IconThought
import UndoManager
import ThoughtEnvelop
import WebThought
RAD_UP = (- math.pi / 2.)
RAD_DOWN = (math.pi / 2.)
RAD_LEFT = (math.pi)
RAD_RIGHT = (0)

MODE_EDITING = 0
MODE_IMAGE = 1
MODE_DRAW = 2
# Until all references of MODE_MOVING are removed...
MODE_MOVING = 999

TYPE_TEXT = 0
TYPE_IMAGE = 1
TYPE_DRAWING = 2
TYPE_NOTE = 3
TYPE_AUDIO = 4
#TYPE_MATH = 5
TYPE_ICON = 6
TYPE_WEB = 7

# TODO: Need to expand to support popup menus
MENU_EMPTY_SPACE = 0

# UNDO actions
UNDO_MOVE = 0
UNDO_CREATE = 1
UNDO_DELETE = 2
UNDO_DELETE_SINGLE = 3
UNDO_COMBINE_DELETE_NEW = 4
UNDO_DELETE_LINK = 5
UNDO_STRENGTHEN_LINK = 6
UNDO_CREATE_LINK = 7

# Note: This is (atm) very broken.  It will allow you to create new canvases, but not
# create new thoughts or load existing maps.
# To get it working either fix the TODO list at the bottom of the class, implement the
# necessary features within all the thought types.  If you do, please send a patch ;)
# OR: Change this class to MMapAreaNew and MMapAreaOld to MMapArea

class MMapArea (gtk.DrawingArea):
    '''A MindMapArea Widget.  A blank canvas with a collection of child thoughts.\
       It is responsible for processing signals and such from the whole area and \
       passing these on to the correct child.  It also informs things when to draw'''

    __gsignals__ = dict (title_changed      = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_STRING, )),
                         open_linked_map     = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_STRING, )),
                        launch_option     = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_STRING, )),
                         doc_save           = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
                         doc_delete         = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               ()),
                         change_mode        = (gobject.SIGNAL_RUN_LAST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_INT, )),
                         change_buffer      = (gobject.SIGNAL_RUN_LAST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_OBJECT, )),
                         text_selection_changed  = (gobject.SIGNAL_RUN_FIRST,
                                               gobject.TYPE_NONE,
                                               (gobject.TYPE_INT, gobject.TYPE_INT, gobject.TYPE_STRING)),
                         thought_selection_changed = (gobject.SIGNAL_RUN_FIRST,
                                                gobject.TYPE_NONE,
                                                (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
                         set_focus               = (gobject.SIGNAL_RUN_FIRST,
                                                    gobject.TYPE_NONE,
                                                    (gobject.TYPE_PYOBJECT, gobject.TYPE_BOOLEAN)),
                         set_attrs               = (gobject.SIGNAL_RUN_LAST,
                                                    gobject.TYPE_NONE,
                                                    (gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN, pango.FontDescription)),
                        quit                   = (gobject.SIGNAL_RUN_FIRST,
                                                    gobject.TYPE_NONE,
                                                    (gobject.TYPE_OBJECT, )))



    def __init__(self, undo):
        super (MMapArea, self).__init__()

        self.thoughts = []
        self.links = []
        self.selected = []
        self.num_selected = 0
        self.primary = None
        self.editing = None
        self.pango_context = self.create_pango_context()
        self.undo = undo
        self.scale_fac = 1.0
        self.translate = False
        self.translation = [0.0,0.0]
        self.timeout = -1
        self.current_cursor = None
        self.do_filter = True
        self.is_bbox_selecting = False

        self.unending_link = None
        self.nthoughts = 0

        impl = dom.getDOMImplementation()
        self.save = impl.createDocument("http://www.donscorgie.blueyonder.co.uk/labns", "MMap", None)
        self.element = self.save.documentElement
        self.im_context = gtk.IMMulticontext ()

        self.mode = MODE_EDITING
        self.old_mode = MODE_EDITING

        self.connect ("expose_event", self.expose)
        self.connect ("button_release_event", self.button_release)
        self.connect ("button_press_event", self.button_down)
        self.connect ("motion_notify_event", self.motion)
        self.connect ("key_press_event", self.key_press)
        self.connect ("key_release_event", self.key_release)
        self.connect ("scroll_event", self.scroll)
        self.commit_handler = None
        self.title_change_handler = None
        self.moving = False
        self.move_origin = None
        self.move_origin_new = None
        self.motion = None
        self.move_action = None
        self.current_root = []
        self.rotation = 0
        self.movewholearea = False
        self.orig = (0,0)

        self.selection_mode = False
        self.bookmarks = []
        self.bkmk_indice = 0
        self.buttondown = False
        self.set_events (gtk.gdk.KEY_PRESS_MASK |
                         gtk.gdk.KEY_RELEASE_MASK |
                         gtk.gdk.BUTTON_PRESS_MASK |
                         gtk.gdk.BUTTON_RELEASE_MASK |
                         gtk.gdk.POINTER_MOTION_MASK |
                         gtk.gdk.SCROLL_MASK
                        )

        self.set_flags (gtk.CAN_FOCUS)
        
        self.background_color = gtk.gdk.color_parse("white")  
        self.outline_color = gtk.gdk.color_parse("#F0D3B9")  
        self.foreground_color = gtk.gdk.color_parse("black")
        self.bbox_current = None
        self.reversed = False
        self.envelops = []
        self.translation_correction = (0,0)
        self.ul = None
        self.lr = None
        self.current = None

        self.automatic_layout = True
        self.negative = False
        self.search_mode = False
        self.search_string = ''

        self.found_elements = []
        self.found_indice = 0
        self.last_edited = None
        self.link_style = 0
        self.has_grid = False
        self.cuttingroot = None

    def transform_coords(self, loc_x, loc_y):
        if hasattr(self, "transform"):
            return self.transform.transform_point(loc_x, loc_y)
            
    def untransform_coords(self, loc_x, loc_y):
        if hasattr(self, "untransform"):
            return self.untransform.transform_point(loc_x, loc_y)

    def button_down (self, widget, event):
        coords = self.transform_coords (event.get_coords()[0], event.get_coords()[1])
        self.buttondown = True
        ret = False
        obj = self.find_object_at (coords)

        if obj and obj.isenvelop:
            self.envelops.remove(obj)

        if event.button == 2:
            self.original_translation = self.translation
            self.origin_x = event.x
            self.origin_y = event.y
            return
        if obj and obj.want_motion ():
            self.motion = obj
            ret = obj.process_button_down (event, self.mode, coords)
            if event.button == 1 and self.mode == MODE_EDITING:
                self.moving = not (event.state & gtk.gdk.CONTROL_MASK)
                self.move_origin = (coords[0],coords[1])
                self.move_origin_new = self.move_origin
            return ret
        if obj:
            if event.button == 1 and self.mode == MODE_EDITING:
                self.moving = not (event.state & gtk.gdk.CONTROL_MASK)
                self.move_origin = (coords[0],coords[1])
                self.move_origin_new = self.move_origin
            ret = obj.process_button_down (event, self.mode, coords)
        elif event.button == 1 and self.mode == MODE_EDITING:
            self.bbox_origin = coords
            self.is_bbox_selecting = True
        elif event.button == 3:
            ret = self.right_click_action (None, event.get_coords (), MENU_EMPTY_SPACE)
        return ret

    def undo_delete(self):
        action = self.undo.pop()
        self.undo_deletion (action, UndoManager.UNDO)
        

    def undo_move (self, action, mode):
        self.undo.block ()
        move_thoughts = action.args[1]
        old_coords = action.args[0]
        new_coords = action.args[2]
        move_x = old_coords[0] - new_coords[0]
        move_y = old_coords[1] - new_coords[1]
        if mode == UndoManager.REDO:
            move_x = -move_x
            move_y = -move_y
        self.unselect_all ()
        for t in move_thoughts:
            self.select_thought (t, -1)
            t.move_by (move_x, move_y)
        self.undo.unblock ()
        self.invalidate ((old_coords[0], old_coords[1], new_coords[0], new_coords[1]))

    def button_release (self, widget, event):
        self.buttondown = False
        self.movewholearea = False
        coords = self.transform_coords (event.get_coords()[0], event.get_coords()[1])

        ret = False
        if self.is_bbox_selecting:
            self.is_bbox_selecting = False
            self.invalidate ()
            try:
                if abs(self.bbox_origin[0] - coords[0]) > 2.0:
                    return True
            except AttributeError:  # no bbox_current
                pass
                
        if self.translate:
            self.translate = False
            return True
        if self.moving and self.move_action:
            self.move_action.add_arg (coords)
            self.undo.add_undo (self.move_action)
            self.move_action = None
        self.motion = None
        self.moving = False
        self.move_origin = None
        if self.mode == MODE_EDITING:
            self.set_cursor(gtk.gdk.LEFT_PTR)
        else:
            self.set_cursor(gtk.gdk.CROSSHAIR)
        obj = self.find_object_at (coords)
        if event.button == 2:
            self.undo.add_undo (UndoManager.UndoAction (self, UndoManager.TRANSFORM_CANVAS, \
                                                        self.undo_transform_cb,
                                                        self.scale_fac, self.scale_fac, 
                                                        self.original_translation,
                                                        self.translation))

        if obj:
            ret = obj.process_button_release (event, self.unending_link, self.mode, coords)
            if len(self.selected) != 1:
                self.invalidate(obj.get_max_area())
                return ret
        elif self.unending_link or event.button == 1:
            sel = self.selected
            thought = self.create_new_thought (coords)
            lnk = None
            if not thought:
                return True
            if not self.primary:
                self.make_primary (thought)
                self.select_thought (thought, None)
                thought.root = thought
            else:
                self.emit ("change_buffer", thought.extended_buffer)
                self.hookup_im_context (thought)
                # Creating links adds an undo action.  Block it here
                self.undo.block ()
                for x in self.current_root:
                    lnk = self.create_link (x, None, thought)
                    thought.root = x 
                for x in self.selected:
                    x.unselect ()
                self.selected = [thought]
                thought.select ()

            if lnk != None:
                thought.link = lnk
            if lnk != None and thought.ul[0] < self.primary.ul[0]:
                self.reverse_links(lnk,thought)
                
            if self.unending_link:
                self.unending_link.set_child (thought)
                self.links.append (self.unending_link)
                element = self.unending_link.get_save_element ()
                self.element.appendChild (element)
                self.unending_link = None
            self.undo.unblock ()
            act = UndoManager.UndoAction (self, UNDO_CREATE, self.undo_create_cb, thought, sel, \
                                          self.mode, self.old_mode, event.get_coords())


            thought.children.sort(key=lambda obj:obj.ul[1])


            for l in self.links:
                if l.uses (thought):
                    act.add_arg (l)
            if self.undo.peak ().undo_type == UNDO_DELETE_SINGLE:
                last_action = self.undo.pop ()
                action = UndoManager.UndoAction (self, UNDO_COMBINE_DELETE_NEW, self.undo_joint_cb, \
                                                 last_action, act)
                self.undo.add_undo (action)
            else:
                self.undo.add_undo (act)
            self.begin_editing (thought)

        self.invalidate ()
        return ret

    def undo_transform_cb (self, action, mode):
        if mode == UndoManager.UNDO:
            self.scale_fac = action.args[0]
            self.translation = action.args[2]
        else:
            self.scale_fac = action.args[1]
            self.translation = action.args[3]
        self.invalidate ()

    def scroll (self, widget, event):
        scale = self.scale_fac
        if event.direction == gtk.gdk.SCROLL_UP:
            self.scale_fac*=1.1
        elif event.direction == gtk.gdk.SCROLL_DOWN:
            self.scale_fac/=1.1
        self.undo.add_undo (UndoManager.UndoAction (self, UndoManager.TRANSFORM_CANVAS, \
                                                    self.undo_transform_cb,
                                                    scale, self.scale_fac, self.translation,
                                                    self.translation))
        self.invalidate()

    def get_selected_thought_coords(self):
        coords = []
        if len(self.selected) == 1:
            if self.selected[0]!= self.primary and self.selected[0].link.reversed:
                thought = self.selected[0]
                coords.append(thought.lr[0] + 8)
                coords.append(thought.ul[1]+5)
            else:
                thought = self.selected[0]
                coords.append(thought.ul[0] - 16-5)
                coords.append(thought.ul[1]+5)
            return coords
        else:
            coords.append(0)
            coords.append(0)
            return coords
 
    def undo_joint_cb (self, action, mode):
        delete = action.args[0]
        create = action.args[1]

        if mode == UndoManager.UNDO:
            self.undo_create_cb (create, mode)
            self.undo_deletion (delete, mode)
        else:
            self.undo_deletion (delete, mode)
            self.undo_create_cb (create, mode)
        self.invalidate ()

    def add_new_thought(self,type,ismath = False):
        self.insertedandreverse = False
        if len(self.selected) == 1:
            coords = []
            t = self.selected[0]
            if type==1:
                if t.link and t.link.reversed:
                    self.insertedandreverse = True
                    coords.append(t.ul[0] - 4)
                    coords.append((t.ul[1]+t.lr[1])/2.0 - 8)
                else:
                    coords.append(t.lr[0] + 15)
                    coords.append((t.ul[1]+t.lr[1])/2.0 - 8)
            elif type==2:
                if t.link and t.link.reversed:
                    coords.append(t.lr[0]+5)
                    coords.append(t.lr[1]+4)
                else:
                    coords.append(t.ul[0]+5)
                    coords.append(t.lr[1]+4)
            elif type==3:
                if t.link and t.link.reversed:
                    coords.append(t.lr[0]+5)
                    coords.append(t.ul[1]-16)
                else:
                    coords.append(t.ul[0]+5)
                    coords.append(t.ul[1]-16)

            t.unselect()
            if not ismath:
                th = self.create_new_thought(coords)
            else:
                th = self.create_new_thought(coords,TYPE_MATH)
            if type==2 or type==3:
                #should be optimized !
                for x in self.links:
                    if x.haschild(t):
                        t = x.parent
            if th:
                lnk = self.create_link (t,None,th) 
                th.link = lnk
                if self.automatic_layout and th.ul[0] < self.primary.ul[0]:
                    self.reverse_links(lnk,t)
                
                self.select_thought (th, None)
                
                self.begin_editing(th)
                #self.invalidate()
#                self.finish_editing()
#               # recompute positions to make place for new node.
#                current = self.current
#                thgt = self.find_next_to_primary(self.current)
#                self.current = thgt 
#                self.recompute_positions()
#                self.current = current
##old code
#                self.begin_editing(th)
                self.invalidate()
                th.root = t
                th.root.children.sort(key=lambda obj:obj.ul[1])
                self.last_edited = th





    def center_on_thought(self,thought):
        self.translation[0] += self.translation_correction[0] / self.scale_fac
        self.translation[1] += self.translation_correction[1] / self.scale_fac

        t0 = self.primary
        t0lx = (t0.ul[0]+t0.lr[0])/2.0
        t0ly = (t0.ul[1]+t0.lr[1])/2.0
        t1 = thought
        t1lx = (t1.ul[0]+t1.lr[0])/2.0
        t1ly = (t1.ul[1]+t1.lr[1])/2.0

        self.translation_correction = (t1lx-t0lx,t1ly-t0ly)
        self.translation[0] -= self.translation_correction[0] / self.scale_fac
        self.translation[1] -= self.translation_correction[1] / self.scale_fac
        self.invalidate()
        return True
   
    def goto_next_found(self):
        self.found_indice+=1
        if self.found_indice>=len(self.found_elements):
            self.found_indice = 0
        self.center_on_thought(self.found_elements[self.found_indice])
        for t in self.selected:
            t.unselect()
        self.select_thought(self.found_elements[self.found_indice],None)

    def goto_prev_found(self):
        self.found_indice-=1
        if self.found_indice<0:
            self.found_indice = len(self.found_elements)-1

        self.center_on_thought(self.found_elements[self.found_indice])
        for t in self.selected:
            t.unselect()
        self.select_thought(self.found_elements[self.found_indice],None)
  
    def goto_next_bookmark(self):
        self.bkmk_indice+=1
        if self.bkmk_indice>=len(self.bookmarks)-1:
            self.bkmk_indice = 0

        self.center_on_thought(self.bookmarks[self.bkmk_indice])
        for t in self.selected:
            t.unselect()
        self.select_thought(self.bookmarks[self.bkmk_indice],None)


    def goto_prev_bookmark(self):
        self.bkmk_indice-=1
        if self.bkmk_indice<0:
            self.bkmk_indice = len(self.bookmarks)-1

        self.center_on_thought(self.bookmarks[self.bkmk_indice])
        for t in self.selected:
            t.unselect()
        self.select_thought(self.bookmarks[self.bkmk_indice],None)


    def zoom_out(self):
        self.scale_fac /= 1.05 
        self.invalidate()

    def zoom_in(self):
        self.scale_fac *= 1.05
        self.invalidate()

    def move_screen_up(self):
        if not self.ul:
            self.invalidate()
        self.translation[1] -= self.lr[1]/4.0
        self.invalidate()

    def move_screen_down(self):
        if not self.ul:
            self.invalidate()
        self.translation[1] += self.lr[1]/4.0
        self.invalidate()

    def move_screen_left(self):
        if not self.ul:
            self.invalidate()
        self.translation[0] -= self.lr[0]/4.0
        self.invalidate()

    def move_screen_right(self):
        if not self.ul:
            self.invalidate()
        self.translation[0] += self.lr[0]/4.0
        self.invalidate()

    def compute_height(self,thought):
        thought.node_height = 0
        if len(thought.children) == 0:
            if thought.elem_type != "math_thought":
                thought.node_height = 27
            else:
                thought.node_height = thought.lr[1] - thought.ul[1]
        else:
            for child in thought.children:
                thought.node_height += self.compute_height(child)

        return thought.node_height

    def retabulate_reversed(self):
        lnk = self.current.link
        pos = lnk.parent.ul
        newposx = self.current.ul[0] - self.current.lr[0]
        tmplist = []
        tmplist.append(self.current)
        self.move_children_alt_launch(self.current,(newposx,0),tmplist)
        self.update_links_from_list(tmplist)

    def recompute_positions_recursive(self,thought):
        ul = thought.ul
        lr = thought.lr

        pos0 = ((ul[0]+lr[0])/2.0,(ul[1]+lr[1])/2.0)

        node_height = 0 
        for t in thought.children:
            if t.elem_type == "thought" or t.elem_type == "math_thought":
                if len(t.children)>0:
                    self.recompute_positions_recursive(t)
                pos1 =  ((t.ul[0]+t.lr[0])/2.0-8,(t.ul[1]+t.lr[1])/2.0) 
                y = pos0[1] + node_height - (thought.node_height/2.0) + t.node_height/2.0 
                newposy = y - pos1[1] 
                tmplist = []
                tmplist.append(t)
                self.move_children_alt_launch(t,(0,newposy),tmplist)
                self.update_links_from_list(tmplist)
                node_height += t.node_height
         
    def recompute_positions(self):
        ul = self.current.ul
        lr = self.current.lr

        pos0 = ((ul[0]+lr[0])/2.0,(ul[1]+lr[1])/2.0)

        self.current.node_height = 0
        for t in self.current.children:
            self.current.node_height += self.compute_height(t)
        
        node_height = 0 
        for t in self.current.children:
            if t.elem_type == "thought" or t.elem_type == "math_thought":
                if len(t.children)>0:
                    self.recompute_positions_recursive(t)
                pos1 =  ((t.ul[0]+t.lr[0])/2.0-8,(t.ul[1]+t.lr[1])/2.0) 
                y = pos0[1] + node_height - (self.current.node_height/2.0) + t.node_height/2.0 
                newposy = y - pos1[1] 
                tmplist = []
                tmplist.append(t)
                self.move_children_alt_launch(t,(0,newposy),tmplist)
                self.update_links_from_list(tmplist)
                node_height += t.node_height

        self.invalidate()

    def add_key(self,event):
        if event.keyval == gtk.gdk.keyval_from_name('a'):
            self.search_string += 'a'
        elif event.keyval == gtk.gdk.keyval_from_name('b'):
            self.search_string += 'b'
        elif event.keyval == gtk.gdk.keyval_from_name('c'):
            self.search_string += 'c'
        elif event.keyval == gtk.gdk.keyval_from_name('d'):
            self.search_string += 'd'
        elif event.keyval == gtk.gdk.keyval_from_name('e'):
            self.search_string += 'e'
        elif event.keyval == gtk.gdk.keyval_from_name('f'):
            self.search_string += 'f'
        elif event.keyval == gtk.gdk.keyval_from_name('g'):
            self.search_string += 'g'
        elif event.keyval == gtk.gdk.keyval_from_name('h'):
            self.search_string += 'h'
        elif event.keyval == gtk.gdk.keyval_from_name('i'):
            self.search_string += 'i'
        elif event.keyval == gtk.gdk.keyval_from_name('j'):
            self.search_string += 'j'
        elif event.keyval == gtk.gdk.keyval_from_name('k'):
            self.search_string += 'k'
        elif event.keyval == gtk.gdk.keyval_from_name('l'):
            self.search_string += 'l'
        elif event.keyval == gtk.gdk.keyval_from_name('m'):
            self.search_string += 'm'
        elif event.keyval == gtk.gdk.keyval_from_name('n'):
            self.search_string += 'n'
        elif event.keyval == gtk.gdk.keyval_from_name('o'):
            self.search_string += 'o'
        elif event.keyval == gtk.gdk.keyval_from_name('p'):
            self.search_string += 'p'
        elif event.keyval == gtk.gdk.keyval_from_name('q'):
            self.search_string += 'q'
        elif event.keyval == gtk.gdk.keyval_from_name('r'):
            self.search_string += 'r'
        elif event.keyval == gtk.gdk.keyval_from_name('s'):
            self.search_string += 's'
        elif event.keyval == gtk.gdk.keyval_from_name('t'):
            self.search_string += 't'
        elif event.keyval == gtk.gdk.keyval_from_name('u'):
            self.search_string += 'u'
        elif event.keyval == gtk.gdk.keyval_from_name('v'):
            self.search_string += 'v'
        elif event.keyval == gtk.gdk.keyval_from_name('w'):
            self.search_string += 'w'
        elif event.keyval == gtk.gdk.keyval_from_name('x'):
            self.search_string += 'x'
        elif event.keyval == gtk.gdk.keyval_from_name('y'):
            self.search_string += 'y'
        elif event.keyval == gtk.gdk.keyval_from_name('z'):
            self.search_string += 'z'
        elif event.keyval == gtk.keysyms.space:
            self.search_string += ' '
        elif event.keyval == gtk.keysyms.BackSpace:
            if len(self.search_string)>0:
                self.search_string = self.search_string[0:len(self.search_string)-1]
        
        for t in self.selected:
            t.unselect()
        self.invalidate()

        found_something = False
        lst = []
        for t in self.thoughts:
            if t.elem_type == 'thought':
                mystring = t.text.lower()
                if mystring.find(self.search_string,0,len(mystring))!=-1:
                    lst.append(t)
                    self.select_thought(t,None)
                    self.last_selected = t
                    found_something = True
                    self.found_indice = 0

        self.found_elements = list(set(lst))
        if found_something:
            self.invalidate()


    def cut_selected_element(self):
        if len(self.selected)==1:
            for x in self.links:
                if x.cuttingroot and not x.haschild(self.selected[0]):
                    x.cuttingroot = False

            for x in self.links:
                if x.haschild(self.selected[0]):
                    x.cut_content(self.links)
                    self.invalidate()
                    break

            if x.cuttingroot:
                self.cuttingroot = self.selected[0]

    def paste_cutted_element(self):
        if len(self.selected)==1 and self.cuttingroot != None:
            for x in self.links:
                if x.cuttingroot:
                    self.delete_link (x)
                    break
            
            lnk = self.create_link(self.selected[0],None,self.cuttingroot)
            self.cuttingroot.link = lnk

# move pasted elements :
#            tr0 = self.selected[0].lr[0] - self.cuttingroot.ul[0]
#            tr1 = self.selected[0].lr[1] - self.cuttingroot.lr[1]
#
#            tmplist = []
#            tmplist.append(self.cuttingroot)
#            self.move_children_alt_launch(self.cuttingroot,(tr0,tr1),tmplist)
#            self.update_links_from_list(tmplist)
#
#            if self.automatic_layout and self.primary and self.current and self.primary!=self.current: 
#                current = self.current
#                t = self.find_next_to_primary(self.cuttingroot)
#                self.current = t 
#                self.recompute_positions()
#                self.current = current
#
            self.invalidate()

    def have_found(self):

        self.last_selected.unselect()

        found_something = False
        lst = []
        for t in self.thoughts:
            if t.elem_type == 'thought':
                mystring = t.text.lower()
                if mystring.find(self.search_string,0,len(mystring))!=-1:
                    lst.append(t)
                    found_something = True
                    self.found_indice = 0

        if self.last_selected and self.last_selected.elem_type == 'thought':
            mystring = self.last_selected.text.lower()
            if mystring.find(self.search_string,0,len(mystring))!=-1:
                lst.append(self.last_selected)
                found_something = True
                self.found_indice = 0

        self.found_elements = list(set(lst))
    
    def find_next_to_primary(self,thought):
        lnk = thought.link
        
        if lnk.child == thought:
            t = lnk.parent
            if t != self.primary:
                return self.find_next_to_primary(t)
            else:
                return lnk.child
        elif lnk.parent == thougth:
            t = lnk.child
            if t != self.primary:
                return self.find_next_to_primary(t)
            else:
                return lnk.parent

    def key_press (self, widget, event):
        # this does the job, but a nice lifting could be nice
        if self.search_mode:
            if event.keyval == gtk.gdk.keyval_from_name('Return'):
                self.have_found()
                self.search_string = ''
                self.search_mode = False
                return True
            else:
                self.add_key(event)  
                return True
        if self.selection_mode:
            if event.keyval == gtk.gdk.keyval_from_name('h') and not self.editing:
                for t in self.selected:
                    tmplist = []
                    tmplist.append(t)
                    self.move_children_alt_launch(t,(-15,0),tmplist)
                    self.update_links_from_list(tmplist)
                self.invalidate()
                return True
            elif event.keyval == gtk.gdk.keyval_from_name('j') and not self.editing:
                for t in self.selected:
                    tmplist = []
                    tmplist.append(t)
                    self.move_children_alt_launch(t,(0,15),tmplist)
                    self.update_links_from_list(tmplist)
                self.invalidate()
                return True
            elif event.keyval == gtk.gdk.keyval_from_name('k') and not self.editing:
                for t in self.selected:
                    tmplist = []
                    tmplist.append(t)
                    self.move_children_alt_launch(t,(0,-15),tmplist)
                    self.update_links_from_list(tmplist)
                self.invalidate()
                return True
            elif event.keyval == gtk.gdk.keyval_from_name('l') and not self.editing:
                for t in self.selected:
                    tmplist = []
                    tmplist.append(t)
                    self.move_children_alt_launch(t,(15,0),tmplist)
                    self.update_links_from_list(tmplist)
                self.invalidate()
                return True
            else:
                return self.global_key_handler(event)
        if event.keyval != gtk.gdk.keyval_from_name('n') and event.keyval != gtk.gdk.keyval_from_name('p') and len(self.found_elements)!=0:
            del self.found_elements[0:len(self.found_elements)]
        if event.keyval == gtk.gdk.keyval_from_name('Return'):
            if self.editing:
                self.finish_editing()
                if self.automatic_layout and self.primary and self.current and self.primary!=self.current: 
                    current = self.current
                    t = self.find_next_to_primary(self.current)
                    self.current = t 
                    self.recompute_positions()
                    self.current = current
                else:
                    self.invalidate ()
            else:
                if len(self.selected)==1 and self.selected[0].elem_type != 'thought':
                    self.selected[0].open_content()
                elif len(self.selected)==1 and self.selected[0].am_link_to_map:
                    self.emit("open_linked_map",self.selected[0].text)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('Insert') or (event.keyval == gtk.gdk.keyval_from_name('a') and not self.editing):
            self.add_new_thought(1)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('H') and not self.editing:
            self.emit("launch_option","H")
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('S') and not self.editing:
            self.emit("launch_otpion","S")
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('T') and not self.editing:
            self.emit("launch_option","T")
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('L') and not self.editing:
            if len(self.selected)==1 and self.selected[0].elem_type == 'thought':
                self.selected[0].make_link_to_map()
                self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('G') and not self.editing:
            self.has_grid = not self.has_grid
            self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('s') and not self.editing:
            if len(self.selected) == 1 and self.selected[0].am_primary:
                self.link_style = (self.link_style + 1)%4
                self.invalidate()
            elif len(self.selected) == 1 and self.selected[0].elem_type == 'thought':
                self.selected[0].am_box = not self.selected[0].am_box
                self.selected[0].background_color = self.outline_color
                self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('q') and not self.editing:
            self.emit("quit",self)
        elif event.keyval == gtk.gdk.keyval_from_name('A') and not self.editing:
            self.automatic_layout = not self.automatic_layout
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('o') and not self.editing:
            self.add_new_thought(2)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('x') and not self.editing:
            self.cut_selected_element()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('X') and not self.editing:
            self.paste_cutted_element()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('O') and not self.editing:
            self.add_new_thought(3)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('r') and not self.editing:
            if len(self.selected)==1:
                self.open_or_reverse(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('h') and not self.editing:
            if len(self.selected)==1:# and self.selected[0]!= self.primary:
                tmplist = []
                tmplist.append(self.selected[0])
                self.move_children_alt_launch(self.selected[0],(-15,0),tmplist)
                self.update_links_from_list(tmplist)
                self.update_view(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('l') and not self.editing:
            if len(self.selected)==1:# and self.selected[0]!= self.primary:
                tmplist =[]
                tmplist.append(self.selected[0])
                self.move_children_alt_launch(self.selected[0],(+15,0),tmplist)
                self.update_links_from_list(tmplist)
                self.update_view(self.selected[0])
            return True
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('j') and not self.editing:
            if len(self.selected)==1:# and self.selected[0]!= self.primary:
                tmplist =[]
                tmplist.append(self.selected[0])
                self.move_children_alt_launch(self.selected[0],(0,+15),tmplist)
                self.update_links_from_list(tmplist)
                self.update_view(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('k') and not self.editing:
            if len(self.selected)==1:#and self.selected[0]!= self.primary:
                tmplist =[]
                tmplist.append(self.selected[0])
                self.move_children_alt_launch(self.selected[0],(0,-15),tmplist)
                self.update_links_from_list(tmplist)
                self.update_view(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('D') and not self.editing:
            envelop = ThoughtEnvelop.ThoughtEnvelop(self.save,self.selected,self.outline_color)
            self.envelops.append(envelop)            
            element = envelop.get_save_element ()
            self.element.appendChild (element)
            self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('f') and not self.editing:
            if len(self.selected)==1:
                for x in self.links:
                    if x.haschild(self.selected[0]):
                        x.fold_content(self.links)
                        self.invalidate()
                        break
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('c') and not self.editing:
            if len(self.selected)==1:
                self.center_on_thought(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('m') and not self.editing:
            if len(self.selected)==1:
                if self.selected[0] in self.bookmarks:
                    self.bookmarks.remove(self.selected[0])
                else:
                    self.bookmarks.append(self.selected[0])
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('n') and not self.editing:
            if len(self.found_elements)!=0:
                self.goto_next_found()
            else:
                self.goto_next_bookmark()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('p') and not self.editing:
            if len(self.found_elements)!=0:
                self.goto_prev_found()
            else:
                self.goto_prev_bookmark()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('z') and not self.editing:
            self.zoom_in()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('Z') and not self.editing:
            self.zoom_out()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('u') and not self.editing:
            self.move_screen_up()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('d') and not self.editing:
            self.move_screen_down()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('w') and not self.editing:
            self.move_screen_left()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('b') and not self.editing:
            self.move_screen_right()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('v') and not self.editing:
            self.selection_mode = True
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('t') and not self.editing:
            if self.current !=  self.primary:
                self.recompute_positions()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('B') and not self.editing:
            if len(self.selected)==1:
                self.selected[0].set_bold()
                self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('I') and not self.editing:
            if len(self.selected)==1:
                self.selected[0].set_italics()
                self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('U') and not self.editing:
            if len(self.selected)==1:
                self.selected[0].set_underline()
                self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('E') and not self.editing:
            self.add_new_thought(1,True)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('W') and not self.editing:
            coords = self.get_selected_thought_coords()
            thought = self.create_new_thought(coords,7,0)
            lnk = self.create_link (self.selected[0], None, thought)
            thought.link = lnk
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('N') and not self.editing:
            self.negative = not self.negative
            if not self.negative:
                self.background_color = gtk.gdk.color_parse("white")  
                self.foreground_color = gtk.gdk.color_parse("black")
            else:
                self.background_color = gtk.gdk.color_parse("black")  
                self.foreground_color = gtk.gdk.color_parse("white")
            self.invalidate()
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('M') and not self.editing:
            for t in self.selected:
                t.unselect()
            self.select_thought(self.primary,None)
            self.center_on_thought(self.primary)
            return True
        elif event.keyval == gtk.gdk.keyval_from_name('i') and not self.editing and len(self.selected)==1:
            self.begin_editing(self.selected[0])
            self.invalidate()
            return True

        if self.editing: 
            if not self.im_context.filter_keypress(event):
                self.editing.process_key_press(event,self.mode)
            return True

        return self.global_key_handler(event)

    def key_release (self, widget, event):
        self.im_context.filter_keypress (event)
        return True

    def motion (self, widget, event):
        #no need to handle motion, when button not down
        if not self.buttondown:
            return
        coords = self.transform_coords (event.get_coords()[0], event.get_coords()[1])
        if self.movewholearea:
            self.set_cursor(gtk.gdk.FLEUR)
            tr = (self.orig[0] - event.get_coords()[0], self.orig[1] - event.get_coords()[1])
            self.orig = event.get_coords()
            self.translation[0] -= tr[0] / self.scale_fac
            self.translation[1] -= tr[1] / self.scale_fac
            self.translation_correction = (0,0)
            self.invalidate()
            return True

        if self.motion:
            if self.motion.handle_motion (event, self.mode, coords):
                return True
        obj = self.find_object_at (coords)
        if self.unending_link and not self.is_bbox_selecting:
            self.unending_link.set_end (coords)
            self.invalidate ()
            return True
        elif event.state & gtk.gdk.BUTTON1_MASK and self.is_bbox_selecting:
            self.bbox_current = coords
            self.invalidate()
            
            ul = [ self.bbox_origin[0], self.bbox_origin[1] ]
            lr = [ coords[0], coords[1] ]
            if self.bbox_origin[0] > coords[0]:
                if self.bbox_origin[1] < coords[1]:
                    ul[0] = coords[0]
                    ul[1] = self.bbox_origin[1]
                    lr[0] = self.bbox_origin[0]
                    lr[1] = coords[1]
                else:
                    ul = coords
                    lr = self.bbox_origin
            elif self.bbox_origin[1] > coords[1]:
                ul[0] = self.bbox_origin[0]
                ul[1] = coords[1]
                lr[0] = coords[0]
                lr[1] = self.bbox_origin[1]
                
            # FIXME: O(n) runtime is bad
            for t in self.thoughts:
                if t.ul[0] > ul[0] and t.lr[0] < lr[0] and t.ul[1] > ul[1] and t.lr[1] < lr[1]:
                    if t not in self.selected:
                        self.select_thought(t, gtk.gdk.SHIFT_MASK)
                else:
                    if t in self.selected:
                        t.unselect()
                        self.selected.remove(t)
            return True
        elif self.moving and not self.editing and not self.unending_link:       
            self.set_cursor(gtk.gdk.FLEUR)
            if not self.move_action:
                self.move_action = UndoManager.UndoAction (self, UNDO_MOVE, self.undo_move, self.move_origin,
                                                           self.selected)
            if len(self.selected)==1:
                t = self.selected[0]
                if self.primary != t:
                    tmplist = []
                    tmplist.append(t)
                    self.move_children_opt_launch(t,coords,self.move_origin_new,tmplist)
                    self.update_links_from_list(tmplist)
                    self.update_view(t)
            else:
                for t in self.selected:
                    t.move_by (coords[0] - self.move_origin_new[0], coords[1] - self.move_origin_new[1])

            self.move_origin_new = (coords[0], coords[1])
            self.invalidate ()
            return True
        elif self.editing and event.state & gtk.gdk.BUTTON1_MASK and not obj and not self.is_bbox_selecting:
            # We were too quick with the movement.  We really actually want to
            # create the unending link
            self.create_link (self.editing)
            self.finish_editing ()
        elif event.state & gtk.gdk.BUTTON2_MASK:
            self.translate = True
            self.translation[0] -= (self.origin_x - event.x) / self.scale_fac
            self.translation[1] -= (self.origin_y - event.y) / self.scale_fac
            self.origin_x = event.x
            self.origin_y = event.y
            self.invalidate()
            return True
            
        if obj:
            obj.handle_motion (event, self.mode, coords)
        elif self.mode == MODE_IMAGE or self.mode == MODE_DRAW:
            self.set_cursor(gtk.gdk.CROSSHAIR)
        else:
            self.set_cursor(gtk.gdk.LEFT_PTR)

    def move_children_alt(self,thought,tr):
        thought.move_by(tr[0],tr[1])
        for child in thought.children:
            self.move_children_alt(child,tr)

    def move_children_alt_launch(self,thought,tr,tmplist):
        thought.move_by_opt(tr[0],tr[1])
        for child in thought.children:
            tmplist.append(child)
            self.move_children_alt_opt(child,tr,tmplist)
        if self.automatic_layout and thought.link and ((not thought.link.reversed and thought.ul[0] < self.primary.ul[0]) or (thought.link.reversed and thought.ul[0] >= self.primary.ul[0])):
            self.reverse_links_alt_opt_launch(thought.link,thought)

    def move_children_alt_opt(self,thought,tr,tmplist):
        thought.move_by_opt(tr[0],tr[1])
        for child in thought.children:
            tmplist.append(child)
            self.move_children_alt_opt(child,tr,tmplist)

    def move_children_opt_launch(self,thought,coords,origin,tmplist):
        thought.move_by_opt(coords[0] - origin[0],coords[1]-origin[1])
        for child in thought.children:
            tmplist.append(child)
            self.move_children_opt(child,coords,origin,tmplist)
        if self.automatic_layout and ((not thought.link.reversed and thought.ul[0] < self.primary.ul[0]) or (thought.link.reversed and thought.ul[0] >= self.primary.ul[0])):
            self.reverse_links_alt_opt_launch(thought.link,thought)

    def move_children_opt(self,thought,coords,origin,tmplist):
        thought.move_by_opt(coords[0] - origin[0],coords[1]-origin[1])
        for child in thought.children:
            tmplist.append(child)
            self.move_children_opt(child,coords,origin,tmplist)

    def move_children(self,thought,coords,origin):
        thought.move_by(coords[0] - origin[0],coords[1]-origin[1])
        for child in thought.children:
            self.move_children(child,coords,origin)
 
    def find_object_at (self, coords):
        for x in reversed(self.thoughts):
            if x.includes (coords, self.mode):
                return x
        for x in self.links:
            if x.includes (coords, self.mode):
                return x
        for e in self.envelops:
            if e.includes (coords,self.mode):
                return e

        return None

    def realize_cb (self, widget):
        self.disconnect (self.realize_handle)
        if self.mode == MODE_IMAGE or self.mode == MODE_DRAW:
            self.set_cursor (gtk.gdk.CROSSHAIR)
        else:
            self.set_cursor (gtk.gdk.LEFT_PTR)
        return False

    def set_cursor(self, kind):
        new_cursor = CursorFactory().get_cursor(kind)
        if self.current_cursor != new_cursor:
            self.current_cursor = new_cursor
            self.window.set_cursor(self.current_cursor)

    def set_mode (self, mode):
        if mode == self.mode:
            return
        self.old_mode = self.mode
        self.mode = mode
        self.finish_editing ()
        self.hookup_im_context ()

        if self.window:
            if mode == MODE_IMAGE or mode == MODE_DRAW:
                self.set_cursor (gtk.gdk.CROSSHAIR)
            else:
                self.set_cursor (gtk.gdk.LEFT_PTR)
        else:
            self.realize_handle = self.connect ("realize", self.realize_cb)
        self.mode = mode
        if self.window:
            self.invalidate ()

    def title_changed_cb (self, widget, new_title):
        self.emit ("title_changed", new_title)

    def make_primary (self, thought):
        if self.primary:
            print "Warning: Already have a primary root"
            if self.title_change_handler:
                self.primary.disconnect (self.title_change_handler)
        self.title_change_handler = thought.connect ("title_changed", self.title_changed_cb)
        self.emit ("title_changed", thought.text)
        self.primary = thought
        thought.make_primary ()

    def hookup_im_context (self, thought = None):
        if self.commit_handler:
            self.im_context.disconnect (self.commit_handler)
            self.im_context.disconnect (self.delete_handler)
            self.im_context.disconnect (self.preedit_changed_handler)
            self.im_context.disconnect (self.preedit_end_handler)
            self.im_context.disconnect (self.preedit_start_handler)
            self.im_context.disconnect (self.retrieve_handler)
            self.commit_handler = None
        if thought:
            try:
                self.commit_handler = self.im_context.connect ("commit", thought.commit_text, self.mode)
                self.delete_handler = self.im_context.connect ("delete-surrounding", thought.delete_surroundings, self.mode)
                self.preedit_changed_handler = self.im_context.connect ("preedit-changed", thought.preedit_changed, self.mode)
                self.preedit_end_handler = self.im_context.connect ("preedit-end", thought.preedit_end, self.mode)
                self.preedit_start_handler = self.im_context.connect ("preedit-start", thought.preedit_start, self.mode)
                self.retrieve_handler = self.im_context.connect ("retrieve-surrounding", thought.retrieve_surroundings, \
                                                                 self.mode)
                self.do_filter = True
            except AttributeError:
                self.do_filter = False
        else:
            self.do_filter = False

    def unselect_all (self):
        self.hookup_im_context ()
        for t in self.selected:
            t.unselect ()
        self.selected = []

    def select_link (self, link, modifiers):
        if modifiers and modifiers & gtk.gdk.SHIFT_MASK and len (self.selected) > 1 and self.selected.count (link) > 0:
            self.selected.remove (link)
            link.unselect ()
            return

        self.hookup_im_context()
        if self.editing:
            self.finish_editing ()
        if modifiers and (modifiers & gtk.gdk.SHIFT_MASK or modifiers == -1):
            if self.selected.count (link) == 0:
                self.selected.append (link)
        else:
            for x in self.selected:
                x.unselect ()
            self.selected = [link]
        link.select()
        self.emit("change_buffer", None)
        
        
    def select_thought (self, thought, modifiers):
        self.current = thought
        if modifiers and modifiers & gtk.gdk.SHIFT_MASK and len (self.selected) > 1 and self.selected.count (thought) > 0 and not self.selection_mode:
            self.selected.remove (thought)
            thought.unselect ()
            return
        self.hookup_im_context ()
        if self.editing:
            self.finish_editing ()
        if thought in self.thoughts:
            self.thoughts.remove (thought)
        self.thoughts.append(thought)
        
        if modifiers and (modifiers & gtk.gdk.SHIFT_MASK or modifiers == -1):
            if self.selected.count (thought) == 0:
                self.selected.append (thought)
        else:
            for x in self.selected:
                x.unselect ()
            self.selected = [thought]
        self.current_root = []
        for x in self.selected:
            if x.can_be_parent():
                self.current_root.append(x)
        thought.select ()
        if len(self.selected) == 1:
            self.emit ("thought_selection_changed", thought.background_color, thought.foreground_color)
            try:
                self.emit ("change_buffer", thought.extended_buffer)
            except AttributeError:
                self.emit ("change_buffer", None)
            self.hookup_im_context (thought)
        else:
            self.emit ("change_buffer", None)

    def begin_editing (self, thought):
        if self.editing and thought != self.editing:
            self.finish_editing ()
        do_edit = thought.begin_editing ()
        if do_edit:
            self.editing = thought

    def undo_link_action (self, action, mode):
        self.undo.block ()
        if self.editing:
            self.finish_editing ()
        link = action.args[0]
        if action.undo_type == UNDO_CREATE_LINK:
            if mode == UndoManager.REDO:
                self.element.appendChild (link.element)
                self.links.append (link)
            else:
                self.delete_link (link)
        elif action.undo_type == UNDO_DELETE_LINK:
            if mode == UndoManager.UNDO:
                self.element.appendChild (link.element)
                self.links.append (link)
            else:
                self.delete_link (link)
        elif action.undo_type == UNDO_STRENGTHEN_LINK:
            if mode == UndoManager.UNDO:
                link.set_strength (action.args[1])
            else:
                link.set_strength (action.args[2])

        self.undo.unblock ()
        self.invalidate ()

    def connect_link (self, link):
        link.connect ("select_link", self.select_link)
        link.connect ("update_view", self.update_view)
        link.connect ("popup_requested", self.right_click_action)

    def create_link (self, thought, thought_coords = None, child = None, child_coords = None, strength = 2):
        if child:
            thought.children.append(child)
            for x in self.links:
                if x.connects (thought, child):
                    if x.change_strength (thought, child):
                        self.delete_link (x)
                    return
            link = Links.Link (self.save, parent = thought, child = child, strength = strength)
            self.connect_link (link)
            element = link.get_save_element ()
            self.element.appendChild (element)
            self.links.append (link)
            return link
        else:
            if self.unending_link:
                del self.unending_link
            self.unending_link = Links.Link (self.save, parent = thought, start_coords = thought_coords,
                                             end_coords = child_coords, strength = strength)

            
    def set_mouse_cursor_cb (self, thought, cursor_type):
        if not self.moving:
            self.set_cursor (cursor_type)

    def update_links_from_list(self,tmplist):
        for x in self.links:
            if x.child in tmplist or x.parent in tmplist:
                x.find_ends(x.reversed)

    def update_links_cb (self, thought):
        for x in self.links:
            if x.uses (thought):
                x.find_ends (x.reversed)

    def claim_unending_link (self, thought):
        if not self.unending_link:
            return
        if self.unending_link.parent == thought:
            del self.unending_link
            self.unending_link = None
            return
        for x in self.links:
            if x.connects (self.unending_link.parent, thought):
                old_strength = x.strength
                x.change_strength (self.unending_link.parent, thought)
                new_strength = x.strength
                self.undo.add_undo (UndoManager.UndoAction (self, UNDO_STRENGTHEN_LINK, self.undo_link_action, x, \
                                                            old_strength, new_strength))
                del self.unending_link
                self.unending_link = None
                return

        self.undo.add_undo (UndoManager.UndoAction (self, UNDO_CREATE_LINK, self.undo_link_action, self.unending_link))
        self.unending_link.set_child (thought)
        self.links.append (self.unending_link)
        element = self.unending_link.get_save_element ()
        self.element.appendChild (element)
        self.unending_link = None

    def right_click_action (self, thought, coords, menu_type):
        if len(self.selected)>1:
            envelop = ThoughtEnvelop.ThoughtEnvelop(self.save,self.selected,self.outline_color)
            self.envelops.append(envelop)            
            element = envelop.get_save_element ()
            self.element.appendChild (element)
        else:
            if not thought:
                self.orig = coords
                self.movewholearea = True
                self.set_cursor(gtk.gdk.FLEUR)
            else:
                if thought.islink:
                    thought.fold_content(self.links)
                elif thought.elem_type == 'thought':
                    if thought.link:
                        thought.link.fold_content(self.links)
                else:
                    self.open_or_reverse(thought)
            self.invalidate()

    def open_or_reverse(self,thought):
        ret = thought.open_content()
        if ret:
            self.reverse_links_alt_opt(thought.link,thought)

    def reverse_links_alt_opt_launch(self,x,thought):
        if thought.elem_type == 'thought':
            thought.open_content()

        x.reversed = not x.reversed
        x.find_ends(x.reversed)
        for child in thought.children:
            self.reverse_links_alt_opt(child.link,child)


    def reverse_links_alt_opt(self,x,thought):
        x.reversed = not x.reversed
        x.find_ends(x.reversed)
        for child in thought.children:
            self.reverse_links_alt_opt(child.link,child)

    def reverse_links(self,x,thought):
        if x.haschild(thought):
            x.reversed = not x.reversed
            x.find_ends(x.reversed)
        for child in thought.children:
            self.reverse_links(x,child)


    def finish_editing (self, thought = None):
        if not self.editing or (thought and thought != self.editing):
            return
        self.editing.finish_editing ()
        self.editing = None

    def update_view (self, thought):
        if not self.editing:
            self.invalidate ()
        else:
            x,y,w,h = thought.get_max_area()
            w += 10
            h += 10
            self.invalidate ((x,y,w,h))

    def invalidate (self, transformed_area = None):
        '''Helper function to invalidate the entire screen, forcing a redraw'''
        rect = None
        if not transformed_area:
            alloc = self.get_allocation ()
            self.ul = (0,0)
            self.lr = (alloc.width,alloc.height)
            rect = gtk.gdk.Rectangle (0, 0, alloc.width, alloc.height)
        else:
            self.ul = self.untransform_coords(transformed_area[0], transformed_area[1])
            self.lr = self.untransform_coords(transformed_area[2], transformed_area[3])
            rect = gtk.gdk.Rectangle (int(self.ul[0]), int(self.ul[1]), int(self.lr[0]-self.ul[0]), int(self.lr[1]-self.ul[1]))
        self.window.invalidate_rect (rect, True)

    def expose (self, widget, event):
        '''Expose event.  Calls the draw function'''
        context = self.window.cairo_create ()
        self.draw (event, context)
        return False

    def draw_grid(self,context,alloc):
        cwidth = context.get_line_width()
        context.set_line_width(0.2)
        ax =  - alloc.width/2 * self.scale_fac + alloc.width/2
        ay =  - alloc.height/2 * self.scale_fac + alloc.height/2
        width  = alloc.width
        height = alloc.height

        scale = 20 * self.scale_fac
        n = 0
        while n*scale < ax:
            n+=1
 
        p = 0
        while p*scale < ay:
            p+=1

        for i in range(0,int(width/scale+100)):
            context.move_to(ax+i*scale-n*scale,ay-p*scale)
            context.line_to(ax+i*scale-n*scale,ay+height+100-p*scale)

        for i in range(0,int(height/scale+100)):
            context.move_to(ax-n*scale,ay+i*scale-p*scale)
            context.line_to(ax+width+100-n*scale,ay+i*scale-p*scale)

        context.stroke()
        context.set_line_width(cwidth)
        
    def draw (self, event, context):
        '''Draw the map and all the associated thoughts'''
        area = event.area
        context.rectangle (area.x, area.y, area.width, area.height)
        context.clip ()
        if not self.negative:
            context.set_source_rgb (1.0,1.0,1.0)
        else:
            context.set_source_rgb(0.0,0.0,0.0)
        context.move_to (area.x, area.y)
        context.paint ()
        if self.negative:
            context.set_source_rgb(1.0,1.0,1.0)
        else:
            context.set_source_rgb (0.0,0.0,0.0)
        
        
        alloc = self.get_allocation ()
        if self.has_grid:
            self.draw_grid(context,alloc)
        context.translate(alloc.width/2., alloc.height/2.)
        context.scale(self.scale_fac, self.scale_fac)
        context.translate(-alloc.width/2., -alloc.height/2.)        
        context.translate(self.translation[0], self.translation[1])

        #if self.has_grid:
        #    self.draw_grid(context,area)

        if self.is_bbox_selecting:
            xs = self.bbox_origin[0]
            ys = self.bbox_origin[1]
            context.set_line_width(0.2)
            if self.bbox_current != None:
                context.rectangle(xs, ys, self.bbox_current[0] - xs, self.bbox_current[1] - ys)
            context.stroke()
            context.set_line_width(1.5)

        for e in self.envelops:
            e.draw(context,self.negative)
 
        for l in self.links:
            l.draw (context,self.link_style,self.negative)
            
        if self.unending_link:
            self.unending_link.draw (context,self.link_style,self.negative)
        
        self.untransform = context.get_matrix()
        self.transform = context.get_matrix()
        self.transform.invert()

        ax, ay = self.transform_coords(area.x, area.y)
        width  = area.width / self.scale_fac
        height = area.height / self.scale_fac
        for t in self.thoughts:
            try:
                if t.lr[0] >= ax and t.ul[0] <= ax + width and t.lr[1] <= ay + height and t.ul[1] >= ay:
                    t.draw (context,self.negative)
            except:
                t.draw(context,self.negative)
        
    def undo_create_cb (self, action, mode):
        self.undo.block ()
        if mode == UndoManager.UNDO:
            if action.args[0] == self.editing:
                self.editing = None
            self.unselect_all ()
            for t in action.args[1]:
                self.select_thought (t, -1)
            self.delete_thought (action.args[0])
            self.emit ("change_mode", action.args[3])
        else:
            self.emit ("change_mode", action.args[2])
            thought = action.args[0]
            self.thoughts.append (thought)
            for t in action.args[1]:
                self.unselect_all ()
                self.select_thought (t, -1)
            self.hookup_im_context (thought)
            self.emit ("change_buffer", thought.extended_buffer)
            self.element.appendChild (thought.element)
            for l in action.args[5:]:
                self.links.append (l)
                self.element.appendChild (l.element)

            self.begin_editing (thought)
        self.emit ("set_focus", None, False)
        self.undo.unblock ()
        self.invalidate ()

    def create_new_thought (self, coords, thought_type = None,icon_number = 0 ,loading = False):
        if self.editing:
            self.editing.finish_editing ()
        if thought_type!= None:
            type = thought_type
        else:
            type = self.mode

        if type == TYPE_TEXT:
            thought = TextThought.TextThought (coords, self.pango_context, self.nthoughts, self.save, self.undo, loading, self.background_color, self.foreground_color)
        elif type == TYPE_IMAGE:
            thought = ImageThought.ImageThought (coords, self.pango_context, self.nthoughts, self.save, self.undo, loading, self.background_color)
        elif type == TYPE_NOTE:
            thought = NoteThought.NoteThought (coords, self.pango_context, self.nthoughts, self.save, self.undo, loading, self.background_color)
        elif type == TYPE_AUDIO:
            thought = AudioThought.AudioThought (coords, self.pango_context, self.nthoughts, self.save, self.undo, loading, self.background_color)
#        elif type == TYPE_MATH:
#            thought = MathThought.MathThought(coords,self.pango_context,self.nthoughts,self.save,self.undo,loading,self.background_color)
        elif type == TYPE_ICON:
            thought = IconThought.IconThought(coords,self.pango_context,self.nthoughts,self.save,self.undo,loading,self.background_color,icon_number)
        elif type == TYPE_WEB:
            thought = WebThought.WebThought(coords,self.pango_context,self.nthoughts,self.save,self.undo,loading,self.background_color)

        if not thought.okay ():
            return None


        if type == TYPE_IMAGE:
            self.emit ("change_mode", self.old_mode)
        elif type == TYPE_NOTE:
            self.emit ("change_mode",self.old_mode)
        elif type == TYPE_AUDIO:
            self.emit ("change_mode",self.old_mode)


        self.nthoughts += 1
        element = thought.element
        self.element.appendChild (thought.element)
        thought.connect ("select_thought", self.select_thought)
        thought.connect ("begin_editing", self.begin_editing)
        thought.connect ("popup_requested", self.right_click_action)
        thought.connect ("create_link", self.create_link)
        thought.connect ("claim_unending_link", self.claim_unending_link)
        thought.connect ("update_view", self.update_view)
        thought.connect ("finish_editing", self.finish_editing)
        thought.connect ("delete_thought", self.delete_thought)
        thought.connect ("text_selection_changed", self.text_selection_cb)
        thought.connect ("change_mouse_cursor", self.set_mouse_cursor_cb)
        thought.connect ("update_links", self.update_links_cb)
        thought.connect ("grab_focus", self.regain_focus_cb)
        thought.connect ("update-attrs", self.update_attr_cb)
        
        self.thoughts.append (thought)

        return thought

    def regain_focus_cb (self, thought, ext):
        self.emit ("set_focus", None, ext)

    def update_attr_cb (self, widget, bold, italics, underline, pango_font):
        self.emit ("set_attrs", bold, italics, underline, pango_font)

    def delete_thought (self, thought,action = None):
        if not action:
            action = UndoManager.UndoAction (self, UNDO_DELETE_SINGLE, self.undo_deletion, [thought])
        rem_links = []
        for l in self.links:
            if l.uses (thought):
                action.add_arg (l)
                rem_links.append (l)
        for l in rem_links:
            self.delete_link (l)
        if thought.element in self.element.childNodes:
            self.element.removeChild (thought.element)
        self.thoughts.remove (thought)
        try:
            self.selected.remove (thought)
        except:
            pass
        if self.editing == thought:
            self.hookup_im_context ()
            self.editing = None
        if self.primary == thought:
            thought.disconnect (self.title_change_handler)
            self.title_change_handler = None
            self.primary = None
            if self.thoughts:
                self.make_primary (self.thoughts[0])
        self.undo.add_undo (action)
        return True

    def undo_deletion (self, action, mode):
        self.undo.block ()
        if mode == UndoManager.UNDO:
            self.unselect_all ()
            for t in action.args[0]:
                self.thoughts.append (t)
                self.select_thought (t, -1)
                self.element.appendChild (t.element)
            for l in action.args[1:]:
                self.links.append (l)
                self.element.appendChild (l.element)
            if action.undo_type == UNDO_DELETE_SINGLE:
                self.begin_editing (action.args[0][0])
                self.emit ("change_buffer", action.args[0][0].extended_buffer)
                if not self.primary:
                    self.make_primary (action.args[0][0])
            else:
                self.emit ("change_buffer", None)
        else:
            for t in action.args[0]:
                self.delete_thought (t)
            for l in action.args[1:]:
                self.delete_link (l)
        self.emit ("set_focus", None, False)
        self.undo.unblock ()
        self.invalidate ()

    def delete_recursive(self,thought,action):
        for child in thought.children:
            self.delete_recursive(child,action)
            #action.add_arg(child)
            self.delete_thought(child,action)

    def delete_selected_elements (self):
        if len(self.selected) != 1:
            return
        action = UndoManager.UndoAction (self, UNDO_DELETE, self.undo_deletion, copy.copy(self.selected))
        # delete_thought as a callback adds it's own undo action.  Block that here
        self.undo.block ()
        t = self.selected[0]
        for child in t.children:
            self.delete_recursive(child,action)
            #action.add_arg(child)
            #for x in self.links:
            #    if x.haschild(child):
            #        self.delete_link(x)
            self.delete_thought(child,action)
        if t.root and t.root.children:
            t.root.children.remove(t)

        #action.add_arg(t)
#        for x in self.links:
#            if x.haschild(t):
#                self.delete_link(x)
        self.delete_thought(t,action)
        self.undo.unblock ()
        self.undo.add_undo (action)
        self.invalidate ()

    def delete_link (self, link):
        if link.element in self.element.childNodes:
            self.element.removeChild (link.element)
        #link.element.unlink ()
        self.links.remove (link)

    def popup_menu_key (self, event):
        print "Popup Menu Key"

    def find_related_thought (self, radians):
        best = None
        other = None

        if radians == RAD_LEFT:
            initial = self.current
            for x in self.links:
                if x.reversed:
                    if x.parent == initial:
                        other = x.child
                else:
                    if x.child == initial:
                        other = x.parent
            best = other
        if radians == RAD_RIGHT:
            initial = self.current
            for x in self.links:
                if x.reversed:
                    if x.child == initial:
                        other = x.parent
                else:
                    if x.parent == initial:
                        other = x.child
            best = other
        if radians == RAD_UP:
            t = self.current
            if t == self.primary:
                best = t.children[0]
            else:
                i = 0
                for th in t.root.children:
                    if th == t:
                        if i-1>=0:
                            best = t.root.children[i-1]
                        else:
                            best = t.root.children[len(t.root.children)-1]
                        break
                    i = i+1
        if radians == RAD_DOWN:
            t = self.current
            if t == self.primary:
                best = t.children[len(t.children)-1]
            else:
                i = 0
                for th in t.root.children:
                    if th == t:
                        if i>=len(t.root.children)-1:
                            best = t.root.children[0]
                        else:
                            best = t.root.children[i+1]
                        break
                    i=i+1
        if self.selection_mode and best!=None:
            self.select_thought(best,-1)
        return best
                

    def global_key_handler (self, event):
        thought = None
        if event.keyval == gtk.keysyms.period:
            if self.last_edited:
                for t in self.selected:
                    t.unselect()
                self.select_thought(self.last_edited,None)
                self.invalidate()
            return True
        if event.keyval == gtk.keysyms.slash:
            for t in self.selected:
                t.unselect()
            self.search_string = ''
            self.search_mode = True
            return True
        if event.keyval == gtk.keysyms.Up:
            thought = self.find_related_thought (RAD_UP)
            if self.selection_mode:
                self.invalidate()
                return True
        elif event.keyval == gtk.keysyms.Down:
            thought = self.find_related_thought (RAD_DOWN)
            if self.selection_mode:
                self.invalidate()
                return True
        elif event.keyval == gtk.keysyms.Left:
            thought = self.find_related_thought (RAD_LEFT)
            if self.selection_mode:
                self.invalidate()
                return True
        elif event.keyval == gtk.keysyms.Right:
            thought = self.find_related_thought (RAD_RIGHT)         
            if self.selection_mode:
                self.invalidate()
                return True
        elif event.keyval == gtk.keysyms.Delete and not self.editing:
            self.selection_mode = False
            t = self.find_next_to_primary(self.current)
            self.delete_selected_elements ()
            self.current = t 
            self.recompute_positions()
        elif event.keyval == gtk.keysyms.a and event.state & gtk.gdk.CONTROL_MASK:
            self.selection_mode = False
            self.unselect_all ()
            for t in self.thoughts:
                t.select ()
                self.selected.append (t)
        elif event.keyval in (gtk.keysyms.X, gtk.keysyms.x) and \
             event.state & gtk.gdk.CONTROL_MASK:
            self.selection_mode = False
            print('ctrl-x')
        elif event.keyval == gtk.keysyms.plus and event.state & gtk.gdk.CONTROL_MASK:
            self.selection_mode = False
            print('ctrl-+')
        elif event.keyval == gtk.keysyms.minus and event.state & gtk.gdk.CONTROL_MASK:
            self.selection_mode = False
            print('ctrl--')
        else:
            self.selection_mode = False
            return False

        if thought:
            self.select_thought (thought, None)
        self.invalidate ()
        return True

    def load_thought (self, node, type):
        if type=="icon_thought":
            thougth = self.create_new_thought(None, type , 1 , loading = True)
        else:
            thought = self.create_new_thought(None, type , 0 , loading = True)
        thought.load (node)

    def load_link (self, node):
        link = Links.Link (self.save)
        self.connect_link (link)
        link.load (node)
        self.links.append (link)
        element = link.get_save_element ()
        self.element.appendChild (element)

    def load_envelop(self,node):
        envelop = ThoughtEnvelop.ThoughtEnvelop(self.save,None,self.outline_color)
        ret = envelop.load(node)
        if ret:
            element = envelop.get_save_element()
            self.element.appendChild(element)
            self.envelops.append(envelop)

    def load_thyself (self, top_element, doc):
        if top_element.hasAttribute("linkstyle"):
            tmp = top_element.getAttribute ("linkstyle")
            self.link_style=int(tmp)
        if top_element.hasAttribute("grid"):
            self.has_grid = True
        if top_element.hasAttribute("negative"):
            self.negative = True
        for node in top_element.childNodes:
            if node.nodeName == "thought":
                self.load_thought (node, TYPE_TEXT)
            elif node.nodeName == "image_thought":
                self.load_thought (node, TYPE_IMAGE)
            elif node.nodeName == "audio_thought":
                self.load_thought (node, TYPE_AUDIO)
            elif node.nodeName == "note_thought":
                self.load_thought(node,TYPE_NOTE)
            elif node.nodeName == "icon_thought":
                self.load_thought(node,TYPE_ICON)
            elif node.nodeName == "web_thought":
                self.load_thought(node,TYPE_WEB)
#            elif node.nodeName == "math_thought":
#                self.load_thought(node,TYPE_MATH)
            elif node.nodeName == "link":
                self.load_link (node)
            elif node.nodeName == "envelop":
                self.load_envelop(node)
            else:
                print "Warning: Unknown element type.  Ignoring: "+node.nodeName

        self.finish_loading ()

    
    def finish_loading (self):
        # Possible TODO: This all assumes we've been given a proper,
        # consistant file.  It should fallback nicely, but...
        # First, find the primary root:
        for t in self.thoughts:
            if t.am_primary:
                self.make_primary (t)
            if t.am_selected:
                self.selected.append (t)
                t.select ()
            if t.editing:
                self.begin_editing (t)
            if t.identity >= self.nthoughts:
                self.nthoughts = t.identity + 1
        if self.selected:
            self.current_root = self.selected
        else:
            self.current_root = [self.primary]
        if len(self.selected) == 1:
            self.emit ("change_buffer", self.selected[0].extended_buffer)
            self.hookup_im_context (self.selected[0])
            self.emit ("thought_selection_changed", self.selected[0].background_color, \
                   self.selected[0].foreground_color)
        else:
            self.emit ("change_buffer", None)
        del_links = []
        for l in self.links:
            if (l.parent_number == -1 and l.child_number == -1) or \
               (l.parent_number == l.child_number):
                del_links.append (l)
                continue
            parent = child = None
            for t in self.thoughts:
                if t.identity == l.parent_number:
                    parent = t
                elif t.identity == l.child_number:
                    child = t
                if parent and child:
                    break
            l.set_parent_child (parent, child)
            if not l.parent or not l.child:
                del_links.append (l)
            elif l.child:
                l.child.link = l

        for l in del_links:
            self.delete_link (l)

        for t in self.thoughts:
            for l in self.links:
                if l.hasparent(t):
                    l.child.root = t
                    t.children.append(l.child)
            t.children.sort(key=lambda obj:obj.ul[1])

        del_envelops = [] 
        for e in self.envelops:
            for t in self.thoughts:
                e.contains(t)
            ret = e.setBoundings()
            if not ret:
                del_envelops.append(e)

        for e in del_envelops:
            if e.element in self.element.childNodes:
                self.element.removeChild (e.element)
            self.envelops.remove(e)

        if len(self.selected):
            self.current = self.selected[0]
        else:
            self.current = self.thoughts[0]

    def prepare_save(self):
        #doc = self.element.ownerDocument
        #elem = doc.createElement ("linkstyle")
        #            self.element.appendChild (elem)
        self.element.setAttribute ("linkstyle", str(self.link_style))
        if self.has_grid:
            self.element.setAttribute ("grid","true")
        if self.negative:
            self.element.setAttribute("negative","true")

        for t in self.thoughts:
            t.update_save ()
        for l in self.links:
            l.update_save ()
        for e in self.envelops:
            e.update_save ()

    def save_thyself (self):
        self.prepare_save()
        if len(self.thoughts) > 0:
            self.emit ("doc_save", self.save, self.element)
        else:
            self.emit ("doc_delete")

    def text_selection_cb (self, thought, start, end, text):
        self.emit ("text_selection_changed", start, end, text)

    def copy_clipboard (self, clip):
        if len (self.selected) != 1:
            return
        self.selected[0].copy_text (clip)


    def cut_clipboard (self, clip):
        if len (self.selected) != 1:
            return
        self.selected[0].cut_text (clip)


    def paste_clipboard (self, clip):
        if len (self.selected) != 1:
            return
        self.selected[0].paste_text (clip)

    def export_grid(self,context,width,height,move_x,move_y):
        cwidth = context.get_line_width()
        context.set_line_width(0.2)
        ax =  0
        ay =  0

        scale = 20 
        for i in range(0,int(width/scale+100)):
            context.move_to(i*scale+move_x,move_y)
            context.line_to(i*scale+move_x,height+1000+move_y)

        for i in range(0,int(height/scale+100)):
            context.move_to(move_x,i*scale+move_y)
            context.line_to(width+1000+move_x,i*scale+move_y)

        context.stroke()
        context.set_line_width(cwidth)
        

    def export (self, context, width, height, native):
        context.rectangle (0, 0, width, height)
        context.clip ()

        if not self.negative:
            context.set_source_rgb (1.0,1.0,1.0)
        else:
            context.set_source_rgb (0.0,0.0,0.0)

        context.move_to (0,0)
        context.paint ()
        if not self.negative:
            context.set_source_rgb (0.0,0.0,0.0)
        else:
            context.set_source_rgb(1.0,1.0,1.0)

        if not native:
            move_x = self.move_x
            move_y = self.move_y
        else:
            move_x = 0
            move_y = 0
        
        if self.has_grid:
            self.export_grid(context,width,height,move_x,move_y)
        for e in self.envelops:
            e.export(context,move_x,move_y,self.negative)
        for l in self.links:
            l.export (context, move_x, move_y,self.link_style,self.negative)
        for t in self.thoughts:
            t.export (context, move_x, move_y,self.negative)

    def get_max_area (self):
        minx = 999
        maxx = -999
        miny = 999
        maxy = -999

        for t in self.thoughts:
            mx,my,mmx,mmy = t.get_max_area ()
            if mx < minx:
                minx = mx
            if my < miny:
                miny = my
            if mmx > maxx:
                maxx = mmx
            if mmy > maxy:
                maxy = mmy
        # Add a 10px border around all
        self.move_x = 10-minx
        self.move_y = 10-miny
        maxx = maxx-minx+20
        maxy = maxy-miny+20
        return (maxx,maxy)

    def get_selection_bounds (self):
        if len (self.selected) == 1:
            try:
                return self.selected[0].index, self.selected[0].end_index
            except AttributeError:
                return None, None
        else:
            return None, None

    def thoughts_are_linked (self):
        if len (self.selected) != 2:
            return False
        for l in self.links:
            if l.connects (self.selected[0], self.selected[1]):
                return True
        return False

    def link_menu_cb (self):
        if len (self.selected) != 2:
            return
        lnk = None
        for l in self.links:
            if l.connects (self.selected[0], self.selected[1]):
                lnk = l
                break
        if lnk:
            self.undo.add_undo (UndoManager.UndoAction (self, UNDO_DELETE_LINK, self.undo_link_action, lnk))
            self.delete_link (lnk)
        else:
            lnk = self.create_link (self.selected[0], None, self.selected[1])
            self.undo.add_undo (UndoManager.UndoAction (self, UNDO_CREATE_LINK, self.undo_link_action, lnk))
        self.invalidate ()

    def set_bold (self, active):
        if len(self.selected) != 1:
            return
        self.selected[0].set_bold (active)
        self.invalidate()
        
    def set_italics (self, active):
        if len(self.selected) != 1:
            return
        self.selected[0].set_italics (active)
        self.invalidate()
        
    def set_underline (self, active):
        if len(self.selected) != 1:
            return
        self.selected[0].set_underline (active)
        self.invalidate()
    
    def set_background_color(self, color):
        self.outline_color = color

    def set_foreground_color(self, color):
        for s in self.selected:
            s.foreground_color = color
            self.foreground_color = color
        if len(self.selected) > 1:
            self.invalidate()

    def set_font(self, font_name):
        if len (self.selected) == 1 and hasattr(self.selected[0], "set_font"):
            self.selected[0].set_font (font_name)
            self.invalidate()

class CursorFactory:
    __shared_state = {"cursors": {}}

    def __init__(self):
        self.__dict__ = self.__shared_state

    def get_cursor(self, cur_type):
        if not self.cursors.has_key(cur_type):
            cur = gtk.gdk.Cursor(cur_type)
            self.cursors[cur_type] = cur
        return self.cursors[cur_type]

